package audio;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


public class MFCC2 {
	private static DocumentBuilderFactory builderFactory = DocumentBuilderFactory
			.newInstance();
	
	public static void main(String args[]){

		try {
			getMFCC2("China1[Part 1].wav","G:/WorkSpace32/multimedia/");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();		}
	}

	public static String getMFCC2(String srcPath, String dstPath)
			throws Exception {
		String command = "";
		String fileName = "";
		String mfccPath = "";
		String mfcc2Path = "";
		String settingPath = "";
		String tmpDstPath = "";
		String[] cmdargs = null;
		
		File source = new File(srcPath);
		fileName = source.getName();
		fileName = fileName.substring(0, fileName.lastIndexOf("."));
		
		
		if(dstPath.lastIndexOf("/") == (dstPath.length()-1))  tmpDstPath = ""; else tmpDstPath = "/";
		
		//
		mfccPath = dstPath + tmpDstPath + "MFCC/";
		File mfcc =  new File(mfccPath);
		if (!mfcc.exists()){mfcc.mkdir();};

		mfcc2Path = dstPath + tmpDstPath + "MFCC2/";
		File mfcc2 =  new File(mfcc2Path);
		if (!mfcc2.exists()){mfcc2.mkdir();};
		
		//extractor mfcc
		System.out.println("	extract MFCC feature...");
		//配置文件直接放src目录了
		//String path =Thread.currentThread().getContextClassLoader().getResource("").getPath();
		settingPath = Util.classPath + "settings.xml";
		command = "-s@" + settingPath + "@" + mfccPath + fileName +  "@" + srcPath;
		//System.out.println(command);
		cmdargs = command.split("@");
		JAudioCommandLine.execute(cmdargs);
		System.out.println("	extract MFCC feature end.");

		//caculate mfcc2
		System.out.println("	caculate MFCC2 feature...");

		//delete energy dimension,input fileNameFV.xml,output fileName.xml
		deleteEnergyDimension(srcPath,mfccPath + fileName + "FV.xml",mfccPath + fileName + "FV2.xml");
		
		//参数文件已经配置了overlap，不需要再次进行MFCC2计算了	
		//后来发现，jaudio的overlap参数不是很准确，自己计算mfcc2了
		calculate(mfccPath + fileName + "FV2.xml", mfcc2Path + fileName + ".xml");
		System.out.println("	caculate MFCC2 feature end.");
		mfcc2Path = mfcc2Path + fileName + ".xml";
		return mfcc2Path;
	}

	//delete energy dimension of mfcc
	private static void deleteEnergyDimension(String srcWav, String srcXML,String dstXML){
		try {
			File target = new File(dstXML);
            FileReader fr = new FileReader(srcXML);
            BufferedReader br = new BufferedReader(fr);
            OutputStream file = new FileOutputStream(target.getAbsolutePath());
            String myreadline;
            boolean delEnergy = false;
            while (br.ready()) {
                myreadline = br.readLine();
                
                //next line is energy dimension
                if(myreadline.contains("<name>MFCC")){
                	delEnergy = true;
                }
                
                //delete first <v>
                if(delEnergy && myreadline.contains("<v>")){
                	myreadline = "";
                	delEnergy = false;
                }
                
                //chinese encode error
                if(myreadline.contains("<data_set_id>")){
                	myreadline = "<data_set_id>" + srcWav + "</data_set_id>";
                }
                file.write(myreadline.getBytes());
            }
            br.close();
            fr.close();
            file.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
	
	/**
	 * @param args
	 * @throws Exception
	 */
	public static void calculate(String mfccPath, String mfcc2Path)
			throws Exception {
		// TODO Auto-generated method stub

		Document document = parse(mfccPath);
		Element rootElement = document.getDocumentElement();
		NodeList sectionList = rootElement.getElementsByTagName("section");

		if (sectionList != null) {
			int len = sectionList.getLength();
			double[][] mfcc = new double[len][12];
			double[][] mfcc2 = new double[len][12];
			Element[] section = new Element[len];
			// get mfcc
			for (int i = 0; i < len; i++) {
				// current MFCC
				section[i] = (Element) sectionList.item(i);
				mfcc[i] = getValues(section[i]);
			}

			// calculate MFCC
			mfcc2 = calMFCC2(mfcc);

			// set mfcc2
			for (int i = 0; i < len; i++) {
				// current MFCC
				setValues(section[i], mfcc2[i]);
			}

			// save mfcc2
			saveMFCC2(document, mfcc2Path);
		}
	}

	// get mfcc from xml
	private static double[] getValues(Element section) {

		double values[] = null;

		NodeList fetureList = section.getElementsByTagName("feature");
		Element feature = (Element) fetureList.item(0);
		NodeList valueList = feature.getElementsByTagName("v");

		if (valueList != null) {
			int len = valueList.getLength();
			values = new double[len];
			for (int i = 0; i < len; i++) {
				Node value = valueList.item(i);
				if (value.getTextContent() != null) {
					values[i] = Double.parseDouble(value.getTextContent());
				}
			}
		}
		return values;
	}

	// calculate MFCC2(t) = 2MFCC(t+2) + MFCC(t+1) - MFCC(t-1) - 2MFCC(t-2)
	private static double[][] calMFCC2(double[][] mfcc) {

		double[][] mfcc2 = new double[mfcc.length][12];

		for (int i = 0; i < mfcc.length; i++) {
			//without the first two and last two 
			if(i < 2 || i > mfcc.length - 3){
				for (int j = 0; j < 12; j++) {
					mfcc2[i][j] = mfcc[i][j];//0;//
				}
			}else{
				for (int j = 0; j < 12; j++) {
					mfcc2[i][j] = 2 * mfcc[i + 2][j] + mfcc[i + 1][j]
					              - mfcc[i - 1][j] - 2 * mfcc[i - 2][j];//mfcc[i][j];//
				}
			}
		}
		return mfcc2;
	}

	// set mfcc from xml
	private static void setValues(Element section, double[] values) {

		NodeList fetureList = section.getElementsByTagName("feature");
		Element feature = (Element) fetureList.item(0);
		NodeList valueList = feature.getElementsByTagName("v");

		if (valueList != null) {
			int len = valueList.getLength();
			for (int i = 0; i < len; i++) {
				Node value = valueList.item(i);
				if (value != null) {
					value.setTextContent(Double.toString(values[i]));
				}
			}
		}
	}

	// Save mfcc2 reference:http://www.ibm.com/developerworks/cn/java/l-javaxml/
	private static void saveMFCC2(Document document, String savePath)
			throws IOException {

		// 首先创建一个 DOMSource 对象 , 该构造函数的参数可以是一个 Document 对象
		// document 代表更改后的 DOM Tree。
		DOMSource doms = new DOMSource(document);

		// 创建一个 File 对象 , 代表 DOM Tree 所包含的数据的输出介质 , 这是一个 XML 文件。
		File f = new File(savePath);
		if (f.exists()) {
			f.delete();
		}
		f.createNewFile();
		// 创建一个 StreamResult 对象 , 该构造函数的参数可以取为 File 对象。
		StreamResult sr = new StreamResult(f);
		// 下面调用 JAXP 中的 XSLT 引擎来实现输出 DOM Tree 中的数据到 XML 文件中的功能。
		// XSLT 引擎的输入为 DOMSource 对象 , 输出为 StreamResut 对象。
		try {
			// 首先创建一个 TransformerFactory 对象 , 再由此创建 Transformer 对象。Transformer
			// 类相当于一个 XSLT 引擎。通常我们使用它来处理 XSL 文件 , 但是在这里我们使
			// 用它来输出 XML 文档。
			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer t = tf.newTransformer();
			// 关键的一步 , 调用 Transformer 对象 (XSLT 引擎 ) 的 transform() 方法 ,
			// 该方法的第一
			// 个参数是 DOMSource 对象 , 第二个参数是 StreamResult 对象。
			t.transform(doms, sr);
		} catch (TransformerConfigurationException tce) {
			System.out.println("Transformer Configuration Exception\n-----");
			tce.printStackTrace();
		} catch (TransformerException te) {
			System.out.println("Transformer Exception\n---------");
			te.printStackTrace();
		}
	}

	// Load and parse XML file into DOM
	public static Document parse(String filePath) {
		Document document = null;
		try {
			// DOM parser instance
			DocumentBuilder builder = builderFactory.newDocumentBuilder();
			// parse an XML file into a DOM tree
			document = builder.parse(new File(filePath));
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return document;
	}
}
